﻿using System;
using System.IO;
using Vbyte.Extension;
using System.Security.Cryptography;
using Vbyte.DataSource.Data;

namespace Vbyte.DataSource.Cache
{
    /// <summary>
    /// 基于文件系统的数据缓存
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    public class FileCachedPolicy<T> : ICachePolicy<T, PropertyBindIdentity>
       where T : class, ICacheTimeSupport,IObjectIdentification<PropertyBindIdentity>, new()
    {
        /// <summary>
        /// 新建基于文件系统的数据缓存实例
        /// </summary>
        public FileCachedPolicy()
        {
            //验证实例类型是否有序列化配置属性System.SerializableAttribute
            //所有成员属性如果类型不在 System.TypeCode 则需标记可序列化
        }

        private T instance = new T();
        private Type instanceType = typeof(T);

        /// <summary>
        /// 根据绑定标志ID获取缓存文件路径
        /// </summary>
        /// <param name="identity">该数据的标志</param>
        /// <returns>本地缓存文件路径</returns>
        public static string GetCacheFilePath(PropertyBindIdentity identity)
        {
            Type instanceType = typeof(T);
            byte[] idDat = identity.GetBytes();
            /*
                 命名空间大文件夹
                            类型全称子文件夹
                            MD5签名为文件名 + .bin扩展名
             */
            return String.Concat(AppDomain.CurrentDomain.BaseDirectory, "\\cache\\",
                     instanceType.Namespace, "\\", instanceType.Name, "\\", DataExchangeHelper.MD5(idDat, true), ".bin");
        }

        #region ICachePolicy<T,PropertyBindIdentity> 成员

        /// <summary>
        /// 添加缓存
        /// </summary>
        /// <param name="identity">该数据的标志</param>
        public void AddCache(PropertyBindIdentity identity)
        {
            instance = DataExchangeHelper.Load<T>(identity);
            if (instance != null)
            {
                byte[] fileDat = instance.GetBytes();
                byte[] idDat = identity.GetBytes();
                SaveToFile(GetCacheFilePath(identity), fileDat);
            }
        }

        /// <summary>
        /// 加载数据，如果不使用缓存则更新上次缓存时间。
        /// </summary>
        /// <param name="identity">该数据的标志</param>
        /// <param name="useCache">指示是否从缓存中加载</param>
        /// <returns>从数据源中获取的相关实例</returns>
        public T Load(PropertyBindIdentity identity, bool useCache)
        {
            T targetInstance = new T();
            string localFilePath = GetCacheFilePath(identity);
            bool cacheExits = File.Exists(localFilePath);
            if (useCache && cacheExits)
            {
                byte[] binDat = GetFileBytes(localFilePath);
                targetInstance = binDat.GetObject<T>();
                targetInstance.SetIdentity(identity);
                return targetInstance;
            }

            bool blnSaveItem = false;
            if (!cacheExits) targetInstance = DataExchangeHelper.Load<T>(identity);
            targetInstance.SetIdentity(identity); 
            if (instanceType.GetInterface(typeof(ICacheValidatable).FullName) != null)
            {
                ICacheValidatable validateInstance = targetInstance as ICacheValidatable;
                if (validateInstance.IsValidate())
                {
                    blnSaveItem = true;
                }
            }
            else
            {
                blnSaveItem = true;
            }

            if (blnSaveItem)
            {
                byte[] fileDat = targetInstance.GetBytes();
                SaveToFile(localFilePath, fileDat);
            }
            return targetInstance;
        }

        /// <summary>
        /// 刷新缓存
        /// </summary>
        /// <param name="identity">该数据的标志</param>
        public void RefreshCache(PropertyBindIdentity identity)
        {
            instance = DataExchangeHelper.Load<T>(identity);
            if (instance != null)
            {
                instance.UpdateCacheTimeUTC(DateTime.Now.ToUniversalTime()); 
                byte[] fileDat = instance.GetBytes();
                byte[] idDat = identity.GetBytes();
                SaveToFile(GetCacheFilePath(identity), fileDat);
            }
        }

        /// <summary>
        /// 清空缓存
        /// </summary>
        /// <param name="identity">该数据的标志</param>
        public void RemoveCache(PropertyBindIdentity identity)
        {
            byte[] idDat = identity.GetBytes();
            string localFilePath = GetCacheFilePath(identity);
            if (File.Exists(localFilePath)) File.Delete(localFilePath);
        }

        private double cacheMinutes = 10.00;

        /// <summary>
        /// 获取或设置该缓存策略的缓存分钟数
        /// </summary>
        /// <value></value>
        public double CacheMinutes
        {
            get { return cacheMinutes; }
            set { cacheMinutes = value; }
        }

        private CacheCompareMode _mode = CacheCompareMode.LocalCacheTime;
        /// <summary>
        /// 获取或设置该缓存策略的缓存比较模式
        /// </summary>
        /// <value></value>
        public CacheCompareMode Mode
        {
            get { return _mode; }
            set { _mode = value; }
        }

        #endregion


        private void SaveToFile(string filePath, byte[] fileDat)
        {
            DirectoryInfo di = new DirectoryInfo(Path.GetDirectoryName(filePath));
            if (!di.Exists) di.Create();
            using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                fs.Write(fileDat, 0, fileDat.Length);
                fs.Close();
            }
        }

        private byte[] GetFileBytes(string filePath)
        {
            byte[] binData = new byte[0];
            if (File.Exists(filePath))
            {
                using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                {
                    int totalLen = (int)fs.Length;
                    byte[] fDat = new byte[totalLen];
                    fs.Read(fDat, 0, totalLen);
                    binData = fDat;
                    fs.Close();
                }
            }
            return binData;
        }

    }

}
